Implement rate limiting on Route Handlers without external services using in-memory storage with a Map to track requests by IP, checking limits within a fixed time window and returning 429 responses when exceeded
Rate limiting protects your Next.js Route Handlers from abuse by restricting the number of requests a client can make within a time window. Without external services like Redis or database dependencies, you can implement a simple in-memory rate limiter using JavaScript's Map to track request counts by IP address. This approach works well for single-server deployments and smaller applications where server restarts and memory limits aren't critical concerns.
The fixed window counter algorithm is the simplest to implement. For each client IP, you store their request count and the timestamp of their first request within the current window. When a new request arrives, you check if the current time exceeds the window duration; if so, reset the counter. Then increment the count and compare against the limit. If exceeded, return a 429 Too Many Requests response. This algorithm can be implemented directly in your Route Handler logic or extracted as middleware for reuse.
For applying rate limits across multiple Route Handlers, you can implement the rate limiter in Next.js middleware. This centralizes the logic and ensures consistent protection. The middleware runs before requests reach your Route Handlers, allowing early rejection of over-limit clients. You can configure different limits for different routes by examining the URL path.
IP detection: Behind proxies, use headers like x-forwarded-for to get the real client IP. Always validate and sanitize these headers to prevent spoofing.
Memory management: In-memory maps grow over time. Implement cleanup routines to remove stale entries and prevent memory leaks.
Server restarts: In-memory rate limits reset when your server restarts, which may be acceptable depending on your requirements.
Distributed environments: This approach fails with multiple server instances. For scaled deployments, you need external storage.
Per-user vs per-IP: For authenticated routes, consider limiting by user ID instead of IP for more accurate control.
If you prefer not to write your own implementation, community libraries like limitix provide drop-in in-memory rate limiting for Next.js Route Handlers and middleware. These libraries implement similar Map-based storage with configurable limits and time windows, handling the edge cases like IP extraction and response formatting. They're designed specifically for single-server Next.js deployments and offer a clean API. However, they share the same limitations regarding server restarts and distributed environments.
Multiple server instances: In-memory stores don't sync across servers, allowing clients to exceed limits.
Serverless environments: Functions are ephemeral, so in-memory data doesn't persist between invocations.
High accuracy requirements: Distributed rate limiting requires centralized storage like Redis.
Long-term tracking: In-memory resets on restart, losing history.